using System;
using System.Collections;
using System.IO;
using System.Threading;
using SQLRunnerLib.Project;
using SQLRunnerLib.Services;
using SQLServices.Runners;
using SQLServices.Runners.Events;
using SQLServices.Log;
using SQLServices.Repository;

namespace SQLRunnerLib.Runners
{
	/// <summary>
	/// The main role of the Runner is to execute the scripts against the
	/// SQL Server database. The constructor requires a list of ScriptFolders.
	/// Call the RunScripts method to execute the scripts.
	/// The class can inform of the progress back to the caller using the four
	/// different types of events.
	/// </summary>
	public sealed class Runner : IRunner
	{

		#region Private Instances

		private SQLProject _proj;

		private ISQLDatabase _sqlDatabase;
		private bool _isAsyncExecution = false;
		private bool _cancelExecution = false;

		#endregion
		#region Events

		public event ProgressMsgCreatedEventHandler ProgressMsgCreated;
		public event ScriptExecErrorEventHandler ScriptExecError;
		public event ScriptExecutedEventHandler ScriptExecuted;
		public event ExecutionFinishedEventHandler ExecutionFinished;

		#endregion
		#region Constructors

		public Runner(SQLProject project):base() 
		{
			_proj = project;
			
		}

		#endregion
		#region Public Methods

		public void CancelExecution() {
			_cancelExecution = true;
			string msg = "Runner - Cancel execution message was received";
			ServiceContainer.Logger.LogMessage(msg, MsgLevel.Info);
		}

		public void RunScriptsAsync()
		{
			if ( _proj == null ) return;
			_isAsyncExecution = true;
			// clone the connection so this thread owns the SQLServer object rather 
			// than the UI
			if ( ProgressMsgCreated != null )
			{
				ProgressMsgCreated(this, 
				new ProgressMsgCreatedEventArgs(
					"Connecting to '{0}' database in the '{1}' SQL server", 
					ServiceContainer.SQLConnection.GetDatabase().DatabaseName,
					ServiceContainer.SQLConnection.ServerName));
			}
			_sqlDatabase = ServiceContainer.SQLConnection.GetDatabase().Clone();
			ServiceContainer.Logger.LogMessage("Runner - thread has started");
			RunScripts();
			return;
		}

		public void RunScripts() {
			if ( _proj == null ) return;
			if ( _isAsyncExecution == false ) _sqlDatabase = ServiceContainer.SQLConnection.GetDatabase();
			try
			{
				foreach (Folder folder in _proj.Folders) {
					ServiceContainer.Logger.LogMessage(
						String.Format("Runner - processing folder {0}", folder.Path));
					ExecFolder(folder, ServiceContainer.CreatePathItem(folder.Path, null), _proj);
				}
			}
			catch //(ThreadInterruptedException ex)
			{
				if ( ProgressMsgCreated != null )
				{
					string msg = "Runner - Script processor was interrupted";
					ProgressMsgCreated(this, 
					                   new ProgressMsgCreatedEventArgs(msg));
					ServiceContainer.Logger.LogMessage(msg, MsgLevel.Info);
					return;
				}
			}
			finally
			{
				if ( ExecutionFinished != null ) 
				{
					ExecutionFinished(this, new ExecutionFinishedEventArgs());
				}
				if ( _isAsyncExecution )
				{
					ServiceContainer.Logger.LogMessage("Runner - thread has finished");
					_sqlDatabase.SQLConnection.Disconnect();
					_sqlDatabase = null;
				}
			}
			return;
		}

		#endregion
		#region Private Methods

		private void ExecFolder(Folder folder, IPathItem pathItem, SQLProject project)
		{
			ArrayList files = SQLProject.FilesInRootFolder(folder, project);

			ServiceContainer.EventLogger.DefaultPath = pathItem;
			foreach (FileInfo file in files)
			{
				ServiceContainer.Logger.LogMessage(
					String.Format("Runner - calling ExecuteSQL method for script {0}", file.Name));
				ExecuteSQL(file);
				if ( _isAsyncExecution ) Thread.Sleep(10);
				if ( _cancelExecution ) return;
			}
			// check if we need to run the subfolders
			if (!folder.IncludeAllSubFolders) return;
			foreach (Folder subfolder in folder.Folders)
			{
				DirectoryInfo dir = new DirectoryInfo(subfolder.Path);
				if ( !dir.Exists ) continue;
				ExecFolder(subfolder, ServiceContainer.CreatePathItem(dir.Name, pathItem), project);
				if ( _cancelExecution ) return;
			}
		}

		private void ExecuteSQL(FileInfo aFile)
		{
			if ( _cancelExecution ) return;
			try
			{
				OnProgressMsgCreated("Executing {0} script", aFile.Name);
				using (StreamReader aStream = new StreamReader(aFile.OpenRead()))
				{
					try
					{
						string strSQL = aStream.ReadToEnd(); aStream.Close();
						_sqlDatabase.Execute(strSQL);
						OnScriptExecuted(aFile.Name);
					}
					catch (Exception e)
					{
						OnProgressMsgCreated("Error when executing the {0} script file", aFile.Name);
						OnScriptError(aFile.Name, e.Message);
					}
				}
			}
			catch (Exception e)
			{
				OnProgressMsgCreated("Error when handling the {0} script file", aFile.Name);
				OnScriptError(aFile.Name, e.Message);
			}
		}

		private void OnProgressMsgCreated( string msg, params object[] args)
		{
			if ( ProgressMsgCreated != null )
			{
				ProgressMsgCreated(this, new ProgressMsgCreatedEventArgs(msg, args));
			}
		}

		private void OnScriptExecuted(string fileName)
		{
			if ( ScriptExecuted != null)
			{
				ScriptExecuted(this, new ScriptExecutedEventArgs(fileName));
			}
		}

		private void OnScriptError(string fileName, string errorMsg)
		{
			if ( ScriptExecError != null)
			{
				ScriptExecError(this, new ScriptExecutionErrorEventArgs(fileName, errorMsg));
			}
		}

		#endregion

	}
}